﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Reflection;

using ServiceContainer = System.ComponentModel.Design.ServiceContainer;

namespace GaleSong
{
    public static class Global
    {
        #region Field

        private static readonly AppBootstrapper appBootstrapper = new AppBootstrapper();

        #endregion

        public static Assembly EntryAssembly;

        public static Action<object> Initalize = o =>
        {
            MainService = new MainService();

            appBootstrapper.Init();

            appBootstrapper.AddService(MainService);

            IoC.BuildUp(MainService);
        };

        public static void AddService(Type serviceType, object serviceInstance)
        {
            appBootstrapper.AddService(serviceType, serviceInstance);
        }

        public static void AddService<T>(T serviceInstance)
        {
            appBootstrapper.AddService(typeof(T), serviceInstance);
        }

        public static Action<object> Exit = o => { };

        public static Action<object> Load = o => { };

        public static IMainService MainService { get; set; }
    }

    public class AppBootstrapper
    {
        #region Field

        private readonly ServiceContainer _serviceContainer = new ServiceContainer();
        private CompositionContainer _container;

        #endregion

        #region Init

        public void Init()
        {
            IoC.GetInstance = GetInstance;
            IoC.GetAllInstances = GetAllInstances;
            IoC.BuildUp = BuildUp;
            Configure();
        }

        #endregion

        #region IoC

        public void AddService(Type serviceType, object serviceInstance)
        {
            _serviceContainer.AddService(serviceType, serviceInstance);
        }

        public void AddService<T>(T serviceInstance)
        {
            _serviceContainer.AddService(typeof (T), serviceInstance);
        }

        private void Configure()
        {
            //1. Add the entry assembly first
            var assembly = Global.EntryAssembly != null ? Global.EntryAssembly : Assembly.GetEntryAssembly();
            AssemblySource.Instance.Add(assembly);

            //2. Init catalog for start up.
            var catalog = new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
                );
            catalog.Catalogs.Add(CreateDirectoryCatalog(Environment.CurrentDirectory));

            _container = new CompositionContainer(catalog, CompositionOptions.DisableSilentRejection);
            var batch = new CompositionBatch();

            //3. Inject export values to IoC container and resolve their dependencies if needed.

            IConfigurationService configurationService = new ConfigurationService("GaleSong.config");
            var resourceService = new ResourceService();
            batch.AddExportedValue(configurationService);
            batch.AddExportedValue<IResourceService>(resourceService);
            batch.AddExportedValue(_container);
            batch.AddExportedValue(catalog);

            _container.Compose(batch);

            IoC.BuildUp(resourceService);
            IoC.BuildUp(configurationService);
        }

        private DirectoryCatalog CreateDirectoryCatalog(string path)
        {
            var catalog = new DirectoryCatalog(path, "GaleSong.*.dll");
            AssemblySource.Instance.AddRange(catalog.LoadedFiles.Select(Assembly.LoadFile));
            return catalog;
        }

        protected object GetInstance(Type serviceType, string key)
        {
            object instance = _serviceContainer.GetService(serviceType);
            if (instance != null)
            {
                return instance;
            }

            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            IEnumerable<object> exports = _container.GetExportedValues<object>(contract);

            if (exports.Any())
                return exports.First();

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        protected IEnumerable<object> GetAllInstances(Type serviceType)
        {
            return _container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        protected void BuildUp(object instance)
        {
            _container.SatisfyImportsOnce(instance);
        }

        #endregion
    }
}